#include "Indice_mem.h"
#include <stddef.h>
#include <stdlib.h>
#include "vector_dinamico.h"
#include "ManejadorBits.h"
#include "GeneradorIndice.h"
#include "Indice.h"
#include "hash.h"
#include "FrontCoder.h"
#include "codigos.h"

/* *********************************************************************
 *                DEFINICION DE LOS TIPOS DE DATOS					   *
 * *********************************************************************/

struct _Indice_mem {
	char* nombreRepositorio;
	hash_t* lexicos; // cada dato es un puntero a long!
	vector_dinamico_t* manejadores_datos; // adentro tiene manejadorBits
	ManejadorBits_t* documentos;
};

/* *********************************************************************
 *                	 	  FUNCIONES AUXILIARES						   *
 * *********************************************************************/

static void quemar_manejador (void* manejador) {
	ManejadorBits_destruir ( (ManejadorBits_t*) manejador );
}

static void quemar_vector (void* vector) {
	vector_destruir ( (vector_dinamico_t*) vector );
}

static char* leer_termino (ManejadorBits_t* manejador, char* terminoAnterior) {
	long repetidos = cod_gamma_leer(manejador);
	if (!terminoAnterior) repetidos = 0;
	long distintos = cod_gamma_leer(manejador);	
	
	char* cadena = ManejadorBits_leer_cadena (manejador , distintos);
	if (!cadena) return NULL;
	
	char* termino = obtener_cadena_entera (distintos, repetidos, terminoAnterior, cadena);
	free (cadena);
	return termino;
}

void cargar_archivo_lexico (hash_t* hash, char* ruta) {
	ManejadorBits_t* manejador = ManejadorBits_crear (ruta, false);
	if (!manejador) return;
	
	bool finalizo = false;
	char* terminoAnterior = NULL;
	while (!finalizo) {
		char* termino = leer_termino (manejador, terminoAnterior);
		if (!termino) finalizo = true;
		 else {
			 long* offset = malloc (sizeof (long));
			 *offset = cod_delta_leer (manejador);
			 if (*offset != -1) {
				 if (!terminoAnterior) *offset = 0;
				 hash_guardar (hash, termino, offset);
			 }
		}
		if (terminoAnterior) free (terminoAnterior); // el hash se hace una copia de la clave
		terminoAnterior = termino;
	}
	ManejadorBits_destruir (manejador);
}

hash_t* cargar_hash_lexico (Indice_t* indice) {
	if (!indice) return NULL;
	
	hash_t* hash = hash_crear (free);
	if (!hash) return NULL;
	
	vector_dinamico_t* rutas = Indice_verRutasLexico (indice);
	int largo = vector_cantidad (rutas);
	for ( int i = 0 ; i < largo ; i++ ) {
		char* ruta = vector_obtener (rutas, i);
		cargar_archivo_lexico (hash, ruta);
	}
	
	return hash;
}

vector_dinamico_t* cargar_archivos_datos (vector_dinamico_t* rutasDatos) {
	if (!rutasDatos) return NULL;
	
	int largo = vector_cantidad (rutasDatos);
	vector_dinamico_t* manejadores = vector_crear (largo, quemar_manejador);
	if (!manejadores) return NULL;
	
	for ( int i = 0 ; i < largo ; i++ ) {
		char* ruta = vector_obtener (rutasDatos, i);
		ManejadorBits_t* temp = ManejadorBits_crear (ruta, false);
		if (!temp) {
			vector_destruir (manejadores);
			return NULL;
		}
		vector_guardar (manejadores, i, temp);
	}
	
	return manejadores;
}

/* Devuelve la posicion del vector de manejadores de datos que corresponde
 * con el primer caracter del termino acorde al alphanumerico */
int _obtener_posicion_manejador (char* termino) {
		
	if (!termino) return -1;	
	char caracter = termino[0];
	char alphanum[] = NOMBRES_ARCH;
	
	for( int i = 0; i < CANT_ARCH ; i++ ){
		if ( alphanum[i] == caracter )
			return i;
	}
	return -1;
}

/* Devuelve el offset que corresponde con el termino pasado por parametro
 * del hash */
long _obtener_offset_datos (Indice_mem_t* indice_mem, char* termino) {
	
	if(!termino || !indice_mem) return -1;
	long *offset;
	
	offset = hash_obtener ( indice_mem->lexicos , termino);
	if(!offset) return -1; //si offset es null en caso de termino no existir en el hash
	return *offset;
}

static long cantidad_digitos (int num) {
	if (num < 0) num *= -1;
	int divisor = 1;
	int cociente = num / divisor;
	int digitos = 1;
	while (cociente >= 10) {
		digitos++;
		divisor *= 10;
		cociente = num / divisor;
	}
	return digitos;
}

/* Devuelve un vector con todos los numeros de documentos que corresponden
 * EN FORMATO STRING */
vector_dinamico_t* _obtener_vector_documentos (ManejadorBits_t* manejadorDatos) {
	if(!manejadorDatos) return NULL;	
	long offsetArch, numArch;
	long largo;
	numArch = 0;
	
	long cantDocs = cod_delta_leer(manejadorDatos);
	if (cantDocs == -1 ) return NULL;
	
	vector_dinamico_t* vectorDocs = vector_crear( cantDocs, free);
	if (!vectorDocs) return NULL;
	
	for (int i = 0; i < cantDocs; i++ ){
		
		offsetArch = cod_delta_leer (manejadorDatos);
		if (offsetArch == -1) return NULL;
		
		if (offsetArch == 1) offsetArch = 0;
		
		numArch+= offsetArch;   //Para obtener el numero entero
		largo = cantidad_digitos (numArch);
		char* numeroString = malloc ( largo + 1);		
		sprintf (numeroString, "%ld", numArch);
		
		vector_guardar(vectorDocs,i,numeroString);
	}

	return vectorDocs;
}

/* Pone en un vector todos los numeros de palabras (punteros a int) que
 * corresponden a un documento */
vector_dinamico_t* _obtener_vector_palabras (ManejadorBits_t* manejadorDatos) {
	
	if(!manejadorDatos) return NULL;
	long numPal, offsetPal;
	numPal = 0;
	
	long cantPal = cod_delta_leer(manejadorDatos);
	if (cantPal == -1 ) return NULL;
	
	vector_dinamico_t* vectorPalabras = vector_crear(cantPal,free);
	if (!vectorPalabras) return NULL;
	
	for (int i = 0; i < cantPal; i++ ){
		
		offsetPal = cod_delta_leer (manejadorDatos);
		if (offsetPal == -1) return NULL;
		numPal+= offsetPal;   //Para obtener el numero entero
		long* numero = malloc (sizeof(long));
		*numero = numPal;
		
		vector_guardar(vectorPalabras,i,numero);
	}

	return vectorPalabras;
}

/* CUENTA:   NumeroNuevo = NumeroViejo - normalizador   */
void _normalizar_bloque_palabras (vector_dinamico_t* bloquePalabras, int normalizador) {
	if(!bloquePalabras || !normalizador) return;
	if( normalizador == 0 ) return;
	long* numero;
	
	int largo = vector_cantidad (bloquePalabras);
	for ( int i = 0 ; i < largo ; i++ ){
		numero = vector_obtener( bloquePalabras , i );
		if (!numero) return;
		*numero = *numero - normalizador;
	}
}


/* *********************************************************************
 *                    PRIMITIVAS DEL Indice_mem						   *
 * *********************************************************************/

/* Destruye el Indice_mem */
void Indice_mem_destruir (Indice_mem_t* indice_mem) {
	if (!indice_mem) return;
	
	if (indice_mem->nombreRepositorio) free (indice_mem->nombreRepositorio);
	if (indice_mem->lexicos) hash_destruir (indice_mem->lexicos);
	if (indice_mem->manejadores_datos) vector_destruir (indice_mem->manejadores_datos);
	if (indice_mem->documentos) ManejadorBits_destruir (indice_mem->documentos);
	free (indice_mem);
}

Indice_mem_t* Indice_mem_crear (char* nombreRecibido, Indice_t* indice, char* docs) {
	if ( (!nombreRecibido) || (!indice) || (!docs) ) return NULL;
	
	Indice_mem_t* indice_mem = malloc (sizeof(Indice_mem_t));
	if (!indice_mem) return NULL;
	
	indice_mem->nombreRepositorio = malloc (sizeof (char) * (strlen (nombreRecibido) + 1) );
	indice_mem->nombreRepositorio = strcpy (indice_mem->nombreRepositorio, nombreRecibido);
	
	indice_mem->lexicos = cargar_hash_lexico (indice);
	
	vector_dinamico_t* rutasDatos = Indice_verRutasDatos (indice);
	indice_mem->manejadores_datos = cargar_archivos_datos (rutasDatos);
	indice_mem->documentos = ManejadorBits_crear (docs, false);	
	
	
	if ( (!(indice_mem->lexicos)) || (!(indice_mem->nombreRepositorio))
	|| (!(indice_mem->manejadores_datos)) || (!(indice_mem->documentos)) ) {
			Indice_mem_destruir (indice_mem);
			return NULL;
	}
	
	return indice_mem;
}



char* Indice_mem_obtener_nombre (Indice_mem_t* indice_mem) {

		return (indice_mem->nombreRepositorio);
}

hash_t* Indice_mem_obtener_datos (Indice_mem_t* indice_mem, char* termino, int normalizador) {
	hash_t* datos = hash_crear (quemar_vector);
	if (!datos) return NULL;
	
	int pos = _obtener_posicion_manejador (termino);
	if (pos < 0) {
		hash_destruir (datos);
		return NULL;
	}
	ManejadorBits_t* manejadorDatos = vector_obtener (indice_mem->manejadores_datos, pos);
	
	long offset = _obtener_offset_datos (indice_mem, termino);
	if (!ManejadorBits_seek (manejadorDatos, offset)) {
		hash_destruir (datos);
		return NULL;
	}
	
	vector_dinamico_t* documentosTemp = _obtener_vector_documentos (manejadorDatos);
	int largo = vector_cantidad (documentosTemp);
	for ( int i = 0 ; i < largo ; i++ ) {
		char* doc = vector_obtener (documentosTemp, i);
		
		vector_dinamico_t* bloquePalabras = _obtener_vector_palabras (manejadorDatos);
		_normalizar_bloque_palabras (bloquePalabras, normalizador);
		
		hash_guardar (datos, doc, bloquePalabras);
	}
	
	vector_destruir (documentosTemp);
	return datos;
}

char* Indice_mem_obtener_documento (Indice_mem_t* indice_mem, int offset) {
	if (offset < 0) return NULL;
	
	ManejadorBits_t* manejadorDocs = indice_mem->documentos;
	if (!ManejadorBits_seek (manejadorDocs, offset)) return NULL;
	
	int largo = cod_gamma_leer(manejadorDocs);
	if (largo == -1 ) return NULL;
	
	return ( ManejadorBits_leer_cadena (manejadorDocs, largo) );
}
